using System;
using System.Text;
using System.Data;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Web;

using Framework;

namespace SchemaTrading
{
    //Collection Class (Customisable half)
    public partial class CSeriesList
    {
        #region Filters
        #endregion

        #region Searching (Optional)
        //Represents a simple search box to search PK and any string columns (add overloads as required, based on the pattern below)
        //e.g. public CSeriesList Search(string nameOrId, ) { ...
        public CSeriesList Search(string nameOrId)
        {
            //1. Normalisation
            nameOrId = (nameOrId??string.Empty).Trim().ToLower();
            
            //2. Start with a complete list
            CSeriesList results = this;
            
            //3. Use any available index, such as those generated for fk/bool columns
            //Normal Case - non-unique index (e.g. foreign key)
            //if (int.MinValue != foreignKey) results = results.GetByForeignKey(foreignKey);

            //Special case - unique index (e.g. primary key)
            /*
            if (!string.IsNullOrEmpty(nameOrId)) 
            {
                int id;
                if (int.TryParse(nameOrId, out id))
                {
                    CSeries obj = this.GetById(id);
                    if (null != obj)
                    {
                        results = new CSeriesList(1);
                        results.Add(obj);
                        return results;
                    }
                }
            }
            */
            
            //4. Exit early if remaining (non-index) filters are blank
            if (string.IsNullOrEmpty(nameOrId)) return results; 
            
            //5. Manually search each record using custom match logic, building a shortlist
            CSeriesList shortList = new CSeriesList();
            foreach (CSeries i in results)
                if (Match(nameOrId, i))
                    shortList.Add(i); 
            return shortList;
        }
        //Manual Searching e.g for string-based columns i.e. anything not indexed (add more params if required)
        private bool Match(string name, CSeries obj)
        {
            if (!string.IsNullOrEmpty(name)) //Match any string column
            {
                if (null != obj.SeriesName && obj.SeriesName.ToLower().Contains(name))   return true;
                return false;   //If filter is active, reject any items that dont match
            }
            return true;    //No active filters (should catch this in step #4)
        }
        #endregion

        #region Cloning
        public CSeriesList Clone(CDataSrc target) //, int parentId)
        {
            //No Transaction
            if (target is CDataSrcRemote)
                return Clone(target, null); //, parentId);

            //Transaction
            using (IDbConnection cn = target.Local.Connection())
            {
                IDbTransaction tx = cn.BeginTransaction();
                try
                {
                    CSeriesList clone = Clone(target, tx); //, parentId);
                    tx.Commit();
                    return clone;
                }
                catch
                {
                    tx.Rollback();
                    throw;
                }
            }
        }
        public CSeriesList Clone(CDataSrc target, IDbTransaction txOrNull) //, int parentId)
        {
            CSeriesList list = new CSeriesList(this.Count);
            foreach (CSeries i in this)
                list.Add(i.Clone(target, txOrNull)); //, parentId));  *Child entities must reference the new parent
            return list;
        }
        #endregion
        
        #region Export to Csv
        //Web - Need to add a project reference to System.Web, or comment out these two methods
        public void ExportToCsv(HttpResponse response) { ExportToCsv(response, "Series.csv"); }
        public void ExportToCsv(HttpResponse response, string fileName)
        {
            CDataSrc.ExportToCsv(response, fileName); //Standard response headers
            StreamWriter sw = new StreamWriter(response.OutputStream);
            ExportToCsv(sw);
            sw.Flush();
            response.End();
        }

        //Non-web
        public void ExportToCsv(string filePath)
        {
            StreamWriter sw = new StreamWriter(filePath);
            ExportToCsv(sw);
            sw.Close();
        }

        //Logic
        protected void ExportToCsv(StreamWriter sw)
        {
            string[] headings = new string[] {"SeriesName", "Length", "From", "To", "Total", "Profit", "Returns", "Ann.Returns", "Avg.Exposure", "Max.Exposure", "Mean Closing", "Std.Dev.Closing", "Variance Closing"};
            CDataSrc.ExportToCsv(headings, sw);
            foreach (CSeries i in this)
            {
                object[] data = new object[] { i.SeriesName, i.Length, i.From, i.To, i.Total.ToString("C"), i.Profit.ToString("C"), i.Returns.ToString("P"), i.AnnualisedReturns.ToString("P"), i.AverageExposure.ToString("P"), i.MaxExposure.ToString("P"), i.MeanClosing, i.StdDevClosing, i.VolatilityClosing };
                CDataSrc.ExportToCsv(data, sw);
            }
        }
        #endregion

        #region Total
        public double TotalProfit
        {
            get
            {
                double t = 0;
                foreach (CSeries i in this)
                    t += i.Datas.SimulatedProfit;
                return t;
            }
        }
        public double AverageAnnualisedReturns
        {
            get
            {
                double t = 0;
                foreach (CSeries i in this)
                    t += i.Datas.AnnualisedReturns;
                return t / this.Count;
            }
        }
        public double TotalReturns { get { return this.TotalProfit / this.PoolReal; } }
        public TimeSpan TimeSpan { get { return MaxDate.AddDays(1).Subtract(MinDate); } }
        public int TotalDays { get { return this.Count== 0 ? 0 : (int)TimeSpan.TotalDays; } }
        public double AnnualisedReturns { get { return Math.Pow(TotalReturns + 1, 365 / TimeSpan.TotalDays) - 1; } }
        public double AverageExposure
        {
            get
            {
                double t = 0;
                foreach (CSeries i in this)
                    t += i.AverageExposure;
                return t / this.Count;
            }
        }
        public DateTime MinDate
        {
            get
            {
                DateTime d = DateTime.MaxValue;
                foreach (CSeries i in this)
                    if (i.From < d && i.From != DateTime.MinValue)
                        d = i.From;
                return d;
            }
        }
        public DateTime MaxDate
        {
            get
            {
                DateTime d = DateTime.MinValue;
                foreach (CSeries i in this)
                    if (i.To > d)
                        d = i.To;
                return d;
            }
        }
        private double _maxTotalExposure = double.NaN;
        public double MaxTotalExposure
        {
            get
            {
                if (double.IsNaN(_maxTotalExposure))
                {
                    double max = 0;
                    foreach (double i in TotalExposure)
                        max = Math.Max(max, i);
                    _maxTotalExposure = CSimulation.LEVERAGE * max;
                }
                return _maxTotalExposure;
            }
        }
        private double _averageTotalExposure = double.NaN;
        public double AverageTotalExposure
        {
            get
            {
                if (double.IsNaN(_averageTotalExposure))
                {
                    double sum = 0;
                    foreach (double i in TotalExposure)
                        sum += i;
                    _averageTotalExposure = CSimulation.LEVERAGE * sum / TotalExposure.Count;
                }
                return _averageTotalExposure;
            }
        }
        private List<double> _totalExposure = null;
        private List<double> TotalExposure
        {
            get
            {
                if (null == _totalExposure)
                {
                    lock (this)
                    {
                        _totalExposure = new List<double>(TotalDays);
                        for (DateTime i = MinDate; i <= MaxDate; i = i.AddDays(1))
                        {

                            double te = 0;
                            bool found = false;
                            foreach (CSeries s in this)
                            {
                                CData d = s.Datas.GetByDate(i);
                                if (null != d)
                                {
                                    found = true;
                                    te += d.Exposure;
                                }
                            }
                            te = te / this.Count;
                            if (found)
                                _totalExposure.Add(te);
                        }
                    }
                }
                return _totalExposure;
            }
        }
        public double PoolReal { get { return Count * CSimulation.START ; } }
        public double PoolLeveraged { get { return PoolReal * CSimulation.LEVERAGE; } }
        public double ExposureAdjustedReturns { get { return AverageAnnualisedReturns / AverageExposure; } }
        public double ExposureAdjustedNetReturns { get { return AnnualisedReturns/ AverageTotalExposure; } }

        private List<double> _totals = null;
        public List<double> Totals
        {
            get
            {
                if (null == _totals)
                {
                    lock (this)
                    {
                        _totals = new List<double>(TotalDays);
                        _dates = new List<DateTime>(TotalDays);
                        for (DateTime i = MinDate; i <= MaxDate; i = i.AddDays(1))
                        {
                            double total = 0;
                            bool found = false;
                            foreach (CSeries s in this)
                            {
                                CData d = s.Datas.GetByDate(i);
                                if (null != d)
                                {
                                    total += d.SimulatedTotal;
                                    found = true;
                                }
                                else if (i < s.From || s.Datas.Count == 0)
                                    total += CSimulation.START * CSimulation.LEVERAGE;
                                else
                                    total += s.Datas.Last.SimulatedTotal;
                            }
                            if (found)
                            {
                                _totals.Add(total);
                                _dates.Add(i);
                            }
                        }
                    }
                }
                return _totals;
            }
        }
        private List<DateTime> _dates = null;
        public List<DateTime> Dates { get { if (null == _dates) _totals = Totals; return _dates; } }
        private List<DateTime> _months = null;
        public List<DateTime> Months
        {
            get
            {
                if (null == _months)
                {
                    lock (this)
                    {
                        if (null == _months)
                        {
                            _months = new List<DateTime>();
                            DateTime month = DateTime.MinValue;
                            foreach (DateTime i in Dates)
                            {
                                if (month == DateTime.MinValue || month.Month != i.Month)
                                {
                                    month = i;
                                    _months.Add(i);
                                }
                            }
                        }
                    }
                }
                return _months;
            }
        }
        private List<DateTime> _quarters = null;
        public List<DateTime> Quarters
        {
            get
            {
                if (null == _quarters)
                {
                    lock (this)
                    {
                        if (null == _quarters)
                        {
                            _quarters = new List<DateTime>();
                            DateTime q = DateTime.MinValue;
                            foreach (DateTime i in Months)
                            {
                                if (q == DateTime.MinValue || i.Month %3 == 0)
                                {
                                    q = i;
                                    _quarters.Add(i);
                                }
                            }
                        }
                    }
                }
                return _quarters;
            }
        }
        private int _wins = int.MinValue;
        private int _losses = int.MinValue;
        private int _flat = int.MinValue;
        public int Wins { get { if (int.MinValue == _wins)  DoCount(); return _wins; } }
        public int Losses { get { if (int.MinValue == _losses)DoCount(); return _losses; } }
        public int Flat { get { if (int.MinValue == _flat)  DoCount(); return _flat; } }
        public double WinsPercent { get { return (double)Wins / (double)Length; } }
        public double LossesPercent { get { return (double)Losses / (double)Length; } }
        public double FlatPercent { get { return (double)Flat / (double)Length; } }
        private void DoCount()
        {
            if (this.Count == 0)
                return;
            lock (this)
            {
                _wins = 0;
                _losses = 0;
                _flat = 0;
                double prev = double.NaN;
                foreach (double i in this.Totals)
                {
                    if (double.IsNaN(prev))
                        _flat += 1;
                    else if (i > prev)
                        _wins += 1;
                    else
                        _losses += 1;
                    prev = i;
                }
            }
        }

        private double _low = double.NaN;
        private double _high = double.NaN;
        public double Low { get { if (double.IsNaN(_low))  DoHighLow(); return _low; } }
        public double High { get { if (double.IsNaN(_high)) DoHighLow(); return _high; } }
        public int Length { get { return Totals.Count; } }
        private void DoHighLow()
        {
            if (this.Count == 0)
                return;
            lock (this)
            {
                _low = double.PositiveInfinity;
                _high = 0;
                foreach (double i in this.Totals)
                    if (double.IsNaN(i))
                        continue;
                    else if (_low > i)
                        _low = i;
                    else if (_high < i)
                        _high = i;
            }
        }
        #endregion
    }
}
